package cc.zzone.client;

import cc.zzone.domain.BiliDmProto;
import cc.zzone.domain.ChatConf;
import cc.zzone.domain.PlatformData;
import cc.zzone.util.CookieUtil;
import cc.zzone.util.DecodeUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.Cookie;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.http.*;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 哔哩哔哩弹幕服务监听器
 *
 * @author jinzhengze
 * @since 2023年10月20日
 */
@Slf4j
public class BiliWsClient extends MyWsClient {

    private final Map<String, Object> roomData;
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private BiliWsClient(URI serverUri, Map<String, String> httpHeaders, String key, Map<String, Object> roomData) {
        super(serverUri, httpHeaders, key);
        this.roomData = roomData;
    }

    public static BiliWsClient getInstance(ChatConf conf) throws Exception {
        PlatformData data = PlatformData.mapper().getData(conf.getPlatform());
        if (!StringUtils.hasText(data.getProve()))
            throw new RuntimeException("平台信息未配置，请根据要求配置相关信息");

        String roomUrl = "https://api.live.bilibili.com/room/v1/Room/room_init?id=" + conf.getRoom();
        JSONObject roomObj = new RestTemplate().getForObject(roomUrl, JSONObject.class);
        if (roomObj == null) throw new RuntimeException("获取平台房间信息失败");
        if (roomObj.getInteger("code") > 0)
            throw new RuntimeException(roomObj.getString("msg"));
        Long realRoomId = roomObj.getJSONObject("data").getLong("room_id");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set(HttpHeaders.COOKIE, data.getProve());
        String wsUrl = "https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?type=0&id=" + realRoomId;
        ResponseEntity<JSONObject> exchange = new RestTemplate()
                .exchange(wsUrl, HttpMethod.GET, new HttpEntity<>(headers), JSONObject.class);
        if (exchange.getBody() == null) throw new RuntimeException("获取平台连接信息失败");

        String host = exchange.getBody().getJSONObject("data").getJSONArray("host_list")
                .getJSONObject(0).getString("host");

        Map<String, Object> roomData = new HashMap<>();
        roomData.put("key", conf.getKey());
        roomData.put("roomId", realRoomId);
        roomData.put("token", exchange.getBody().getJSONObject("data").getString("token"));
        roomData.put("host", host);
        List<Cookie> cookies = CookieUtil.parseCookie(data.getProve());
        Cookie cookie = cookies.stream().filter(o -> "DedeUserID".equalsIgnoreCase(o.getName()))
                .findFirst().orElse(null);
        roomData.put("uid", cookie == null ? "0" : cookie.getValue());
        cookie = cookies.stream().filter(o -> "buvid3".equalsIgnoreCase(o.getName()))
                .findFirst().orElse(null);
        roomData.put("buvid", cookie == null ? "" : cookie.getValue());

        URI uri = new URI("wss://" + host + "/sub");
        HashMap<String, String> header = new HashMap<>();
        header.put("Host", host);
        header.put("Origin", "https://live.bilibili.com");
        return new BiliWsClient(uri, header, conf.getKey(), roomData);
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        log.info("【{}】，发送认证请求", roomData.get("key"));
        this.send(getCertification());
    }

    @Override
    public void onMessage(ByteBuffer byteBuffer) {
        // 开启心跳线程
        if (getHeartBeatThread() == null || !getHeartBeatThread().isAlive()) {
            setHeartBeatThread(new Thread(() -> {
                while (Thread.currentThread().isAlive()) {
                    log.info("【{}】，发送心跳包", roomData.get("key"));
                    try { // 延时30s，定时发送心跳包
                        if (this.isOpen()) this.send(heartBeat());
                        else break;
                        TimeUnit.SECONDS.sleep(30);
                    } catch (InterruptedException ignored) {
                    }
                }
            }));
        }
        JSONArray array = decodeMsg(byteBuffer);
        formatMsgAndSend(array);
    }

    /**
     * 认证数据包
     */
    private ByteBuffer getCertification() {
        String CERT = """
                {"uid":%s,"roomid":%s,"buvid":"%s","key":"%s","protover":3,"platform":"web","type":2}""";
        byte[] bytes = String.format(CERT, roomData.get("uid"), roomData.get("roomId"),
                        roomData.get("buvid"), roomData.get("token"))
                .getBytes(StandardCharsets.UTF_8);
        ByteBuffer allocate = ByteBuffer.allocate(bytes.length + 16);
        allocate.putInt(bytes.length + 16);
        allocate.putShort((short) 16);
        allocate.putShort((short) 1);
        allocate.putInt(7);
        allocate.putInt(1);
        for (byte aByte : bytes) allocate.put(aByte);
        allocate.flip();
        return allocate;
    }

    /**
     * 心跳包
     */
    private ByteBuffer heartBeat() {
        byte[] bytes = "[object Object]".getBytes(StandardCharsets.UTF_8);
        ByteBuffer allocate = ByteBuffer.allocate(bytes.length + 16);
        allocate.putInt(bytes.length + 16);
        allocate.putShort((short) 16);
        allocate.putShort((short) 1);
        allocate.putInt(2);
        allocate.putInt(1);
        for (byte aByte : bytes) allocate.put(aByte);
        allocate.flip();
        return allocate;
    }

    /**
     * 数据解析
     */
    private JSONArray decodeMsg(ByteBuffer byteBuffer) {
        JSONArray result = new JSONArray();
        // 空包
        if (byteBuffer.capacity() <= 0) return result;
        int packetLength = byteBuffer.getInt(); // 数据包长度
        int headLength = byteBuffer.getShort(); // 头部长度
        int version = byteBuffer.getShort(); // 协议版本号
        byteBuffer.getShort(); // 数据类型
        byteBuffer.getShort(); // 常数
        byteBuffer.position(headLength);
        byte[] array = new byte[packetLength - headLength];
        byteBuffer.get(array);
        if (version == 2 || version == 3) { // 数据解压缩
            byte[] decode = version == 2 ? DecodeUtil.decompress(array) : DecodeUtil.brotliDecompress(array);
            // 默认考虑粘包，进行数据分离
            ByteBuffer wrap = ByteBuffer.wrap(decode);
            int offset = 0;
            while (offset < decode.length) {
                int len = wrap.getInt(); // 数据包长度
                int hLen = wrap.getShort(); // 头部长度
                byte[] msg = new byte[len - hLen]; // 有效数据字节数组
                wrap.position(hLen + offset);
                wrap.get(msg);
                result.add(JSON.parse(new String(msg)));
                offset += len;
            }
        } else if (!"{\"code\":0}".equals(new String(array))) {
            try {
                result.add(JSONObject.parse(new String(array)));
            } catch (Exception ignored) {
            }
        }

        log.info("【{}】，消息解析完成。result={}", roomData.get("key"), result);
        return result;
    }

    /**
     * 格式化数据，并转发给所有连接的ws客户端
     * 身份字段ID，0用户 1总督 2提督 3舰长 4房管
     */
    private void formatMsgAndSend(JSONArray array) {
        if (array == null || array.size() == 0) return;
        for (Object o : array) {
            JSONObject item = new JSONObject();
            JSONObject tmp = JSON.parseObject(JSON.toJSONString(o));
            String cmd = tmp.getString("cmd");
            // 基础交互 关注，分享，进入
            if ("INTERACT_WORD".equals(cmd)) {
                item.put("cmd", "WELCOME");
                JSONObject data = tmp.getJSONObject("data");
                item.put("username", data.get("uname"));
                item.put("face", getFaceUrl(data.getString("uid")));
                item.put("content", switch (data.getInteger("msg_type")) {
                    case 2 -> "关注了直播间";
                    case 3 -> "分享了直播间";
                    case 4 -> "特别关注了直播间";
                    case 5 -> "互粉了直播间";
                    default -> "进入了直播间";
                });
                item.put("time", sdf.format(new Date(data.getLong("timestamp") * 1000)));
                item.put("nameColor", data.get("uname_color"));
                item.put("contentColor", null);
                item.put("identities", new JSONArray());
                try { // 这里获取的大航海数据 不一定是该直播间主播的
                    Integer guard = tmp.getJSONObject("fans_medal").getInteger("guard_level");
                    item.getJSONArray("identities").add(guard);
                } catch (Exception e) {
                    item.getJSONArray("identities").add(0);
                }
            }
            // 普通留言
            else if ("DANMU_MSG".equals(cmd)) {
                try {
                    byte[] bytes = Base64.getDecoder().decode(tmp.getString("dm_v2").getBytes(StandardCharsets.UTF_8));
                    BiliDmProto.DM dm = BiliDmProto.DM.parseFrom(bytes);
                    item.put("cmd", "MSG");
                    item.put("username", dm.getUser().getUname());
                    item.put("face", dm.getUser().getFace());
                    item.put("content", dm.getContent());
                    item.put("time", sdf.format(new Date(dm.getTimestamp())));
                    item.put("nameColor", dm.getUser().getColor());
                    item.put("contentColor", "#" + Integer.toHexString(dm.getColor()));
                    item.put("identities", new JSONArray());
                    // 大航海信息 1总督 2提督 3舰长
                    item.getJSONArray("identities").add(tmp.getJSONArray("info").getInteger(7));
                    if (tmp.getJSONArray("info").getJSONArray(2).getInteger(2) == 1)
                        item.getJSONArray("identities").add(4);
                } catch (Exception ignored) {
                }
            }
            // 超级留言
            else if ("SUPER_CHAT_MESSAGE".equals(cmd)) {
                item.put("cmd", "SC");
                JSONObject data = tmp.getJSONObject("data");
                System.out.println(data);
                JSONObject userInfo = data.getJSONObject("user_info");
                item.put("username", userInfo.getString("uname"));
                item.put("face", userInfo.getString("face"));
                item.put("content", data.getString("message"));
                item.put("time", sdf.format(new Date(data.getLong("time") * 1000)));
                item.put("nameColor", null);
                item.put("contentColor", data.getString("font_color"));
                item.put("identities", new JSONArray());
                item.getJSONArray("identities").add(userInfo.getInteger("guard_level"));
                // 是否为房管
                if (userInfo.getInteger("manager") != 0) item.getJSONArray("identities").add(4);
                item.put("bounty", data.getInteger("price"));
            }
            // 购买舰长
            else if ("GUARD_BUY".equals(cmd)) {
                //{"data":{"uid":263160725,"start_time":1537077349,"price":198000,"num":1,"gift_id":10003,"end_time":1537077349,"gift_name":"舰长","username":"送君千里带绿帽","guard_level":3},"cmd":"GUARD_BUY"}
                item.put("cmd", "VIP");
                JSONObject data = tmp.getJSONObject("data");
                item.put("username", data.getString("username"));
                item.put("face", getFaceUrl(data.getString("uid")));
                item.put("content", null);
                item.put("time", sdf.format(new Date(data.getLong("start_time") * 1000)));
                item.put("nameColor", null);
                item.put("contentColor", null);
                item.put("identities", new JSONArray());
                item.getJSONArray("identities").add(data.getInteger("guard_level"));
                item.put("giftId", data.getString("gift_id"));
                item.put("gift", data.getString("gift_name"));
                item.put("price", data.getString("price"));
            }
            if (item.keySet().size() == 0) continue;
            try { // 将头像链接转为base64数据对象，防止跨域
                if (item.get("face") != null)
                    item.put("avatar", "data:image/png;base64," + Base64.getEncoder()
                            .encodeToString(new RestTemplate().getForObject(item.getString("face"), byte[].class)));
            } catch (Exception ignored) {
            }
            JSONArray result = new JSONArray();
            result.add(item);
            this.sendMsg(result.toJSONString());
        }
    }

    /**
     * 请求获取用户头像URl
     */
    private String getFaceUrl(String uid) {
        try {
            String url = "https://tenapi.cn/bilibili/?uid=509588137" + uid;
            JSONObject imgData = JSON.parseObject(new RestTemplate().getForObject(url, String.class));
            assert imgData != null;
            return imgData.getJSONObject("data").getString("avatar");
        } catch (Exception ignored) {
            return null;
        }
    }
}
